////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_MATH_INTERNAL_X86_SSE4_1_BASIC_INL__
#define __WN_MATH_INTERNAL_X86_SSE4_1_BASIC_INL__

#ifndef __WN_MATH_BASIC_H__
    #error "x86/SSE4.1/WNBasic.inl should never be included directly. Please include WNBasic.h instead"
#elif !defined _WN_X86
    #error "x86/SSE4.1/WNBasic.inl has been included on a non x86 platform. Please rectify this."
#elif !defined __WN_MATH_ENABLE_EXTENSIONS
    #error "x86/SSE4.1/WNBasic.inl has been included when extensions are disabled. Please rectify this."
#elif !defined __WN_MATH_SSE4_1_ALLOWED
    #error "x86/SSE4.1/WNBasic.inl has been included when SSE4.1 is not allowed. Please rectify this."
#endif

#include <smmintrin.h>

namespace WNMath {
    namespace __WNInternal {
        __WN_MATH_SSE_INLINE WN_FLOAT32 __WNModSSE4_1(const WN_FLOAT32 _dividend, const WN_FLOAT32 _divisor) {
            __m128 dividend = _mm_load_ss(&_dividend);
            __m128 divisor = _mm_load_ss(&_divisor);

            #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                __m128 result = _mm_mul_ss(dividend, _mm_rcp_ss(divisor));
            #else
                __m128 result = _mm_div_ss(dividend, divisor);
            #endif

            __m128 intergerPortion = _mm_round_ss(_mm_setzero_ps(), result, _MM_FROUND_TRUNC);
            __m128 remainder = _mm_sub_ss(result, intergerPortion);

            return(_mm_cvtss_f32(_mm_mul_ss(remainder, divisor)));
        }

        __WN_MATH_SSE2_INLINE WN_FLOAT64 __WNModSSE4_1(const WN_FLOAT64 _dividend, const WN_FLOAT64 _divisor) {
            __m128d dividend = _mm_load_sd(&_dividend);
            __m128d divisor = _mm_load_sd(&_divisor);
            __m128d result = _mm_div_sd(dividend, divisor);
            __m128d intergerPortion = _mm_round_sd(_mm_setzero_pd(), result, _MM_FROUND_TRUNC);
            __m128d remainder = _mm_sub_sd(result, intergerPortion);

            return(_mm_cvtsd_f64(_mm_mul_sd(remainder, divisor)));
        }

        __WN_MATH_SSE_INLINE WN_FLOAT32 __WNRoundSSE4_1(const WN_FLOAT32& _number) {
            __m128 number = _mm_load_ss(&_number);

            return(_mm_cvtss_f32(_mm_round_ss(_mm_setzero_ps(), number, _MM_FROUND_NINT)));
        }

        __WN_MATH_SSE2_INLINE WN_FLOAT64 __WNRoundSSE4_1(const WN_FLOAT64& _number) {
            __m128d number = _mm_load_sd(&_number);

            return(_mm_cvtsd_f64(_mm_round_sd(_mm_setzero_pd(), number, _MM_FROUND_NINT)));
        }

        __WN_MATH_SSE_INLINE WN_FLOAT32 __WNFloorSSE4_1(const WN_FLOAT32& _number) {
            __m128 number = _mm_load_ss(&_number);

            return(_mm_cvtss_f32(_mm_floor_ss(_mm_setzero_ps(), number)));
        }

        __WN_MATH_SSE2_INLINE WN_FLOAT64 __WNFloorSSE4_1(const WN_FLOAT64& _number) {
            __m128d number = _mm_load_sd(&_number);

            return(_mm_cvtsd_f64(_mm_floor_sd(_mm_setzero_pd(), number)));
        }

        __WN_MATH_SSE_INLINE WN_FLOAT32 __WNCeilSSE4_1(const WN_FLOAT32& _number) {
            __m128 number = _mm_load_ss(&_number);

            return(_mm_cvtss_f32(_mm_ceil_ss(_mm_setzero_ps(), number)));
        }

        __WN_MATH_SSE2_INLINE WN_FLOAT64 __WNCeilSSE4_1(const WN_FLOAT64& _number) {
            __m128d number = _mm_load_sd(&_number);

            return(_mm_cvtsd_f64(_mm_ceil_sd(_mm_setzero_pd(), number)));
        }

        __WN_MATH_SSE_INLINE WN_FLOAT32 __WNTruncSSE4_1(const WN_FLOAT32& _number) {
            __m128 number = _mm_load_ss(&_number);

            return(_mm_cvtss_f32(_mm_round_ss(_mm_setzero_ps(), number, _MM_FROUND_TRUNC)));
        }

        __WN_MATH_SSE2_INLINE WN_FLOAT64 __WNTruncSSE4_1(const WN_FLOAT64& _number) {
            __m128d number = _mm_load_sd(&_number);

            return(_mm_cvtsd_f64(_mm_round_sd(_mm_setzero_pd(), number, _MM_FROUND_TRUNC)));
        }
    }
}
#endif // __WN_MATH_INTERNAL_X86_SSE4_1_BASIC_INL__